package com.uselaw.api.security;

import com.uselaw.api.consts.BizConsts;
import com.uselaw.base.autoconfigure.MutiRedisAutoConfiguration;
import com.uselaw.base.constant.RedisConsts;
import com.uselaw.base.http.Resps;
import com.uselaw.base.util.JsonUtil;
import com.uselaw.base.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.util.*;

/**
 * Security Web 配置
 *
 * @ClassName: SecurityConfig
 * @Description:Security Web 配置
 * @author: maxf
 * @date: 2017年11月22日 下午4:40:22
 */
@EnableWebSecurity(debug = false)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private static final String CONSUMER_USERS_BY_USERNAME_QUERY = "SELECT mobile,pwd,consumer_id,is_enable," +
            "is_non_expire,is_non_lock FROM consumer WHERE mobile = ?";
    private static final String CONSUMER_AUTHORITIES_BY_USERNAME_QUERY = "SELECT mobile,jsonb_array_elements_text" +
            "(roles) FROM consumer where mobile=?";

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    @Qualifier(MutiRedisAutoConfiguration.BEAN_REDIS_TEMPLATE0)
    private RedisTemplate<Object, Object> redisTemplate0;


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        UserDetailsService userDetailsService = new ConsumerJdbcDaoImpl(
                jdbcTemplate,
                CONSUMER_USERS_BY_USERNAME_QUERY,
                CONSUMER_AUTHORITIES_BY_USERNAME_QUERY);
        auth.authenticationProvider(new ConsumerAuthenticationProvider(userDetailsService));
        auth.userDetailsService(userDetailsService).passwordEncoder(new Md5PasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.servletApi().disable();
        http.requestCache().disable();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.securityContext().securityContextRepository(new ConsumerSecurityContextRepository(redisTemplate0));
        http.addFilterAt(new ConsumerAuthenticationProcessingFilter(super.authenticationManager()), UsernamePasswordAuthenticationFilter.class);
        http.formLogin().successHandler((request, response, authentication) -> {
            String token = JwtUtil.compact(new ConsumerToken(authentication.getName()));
            ConsumerUser user = (ConsumerUser) authentication.getPrincipal();
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            List<String> roles = new ArrayList<>();
            if (authorities != null && authorities.size() > 0) {
                for (GrantedAuthority g : authorities) {
                    roles.add(g.getAuthority());
                }
            }

            Map<String, Object> m = new HashMap<>(4);
            m.put("username", user.getUsername());
            m.put("token", token);
            m.put("roles", roles);
            m.put("id", user.getId());

            redisTemplate0.opsForHash().putAll(RedisConsts.CONSUMER_SESSION + "-" + user.getUsername(), m);

            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(JsonUtil.obj2Json(Resps.success().setSucc(token)));
            response.getWriter().close();
        }).failureHandler((request, response, exception) -> {
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            Resps resps = new Resps();
            if (exception instanceof DisabledException) {
                resps.setErr(BizConsts.BASE_IS_LOCK_CODE, new String[]{BizConsts.BASE_IS_LOCK_MSG});
            } else if (exception instanceof AccountExpiredException) {
                resps.setErr(BizConsts.BASE_IS_EXPIRE_CODE, new String[]{BizConsts.BASE_IS_EXPIRE_MSG});
            } else if (exception instanceof CredentialsExpiredException) {
                resps.setErr(BizConsts.BASE_LOGIN_IS_EXPIRE_CODE, new String[]{BizConsts.BASE_LOGIN_IS_EXPIRE_MSG});
            } else if (exception instanceof LockedException) {
                resps.setErr(BizConsts.BASE_IS_LOCKED_CODE, new String[]{BizConsts.BASE_IS_LOCKED_MSG});
            } else if (exception instanceof AuthenticationCredentialsNotFoundException) {
                resps.setErr(BizConsts.BASE_CREDENTIALS_NOT_FOUND_CODE, new String[]{BizConsts.BASE_CREDENTIALS_NOT_FOUND_MSG});
            } else if (exception instanceof BadCredentialsException) {
                resps.setErr(BizConsts.BASE_PWD_IS_ERR_CODE, new String[]{BizConsts.BASE_PWD_IS_ERR_MSG});
            } else if (exception instanceof UsernameNotFoundException) {
                resps.setErr(BizConsts.BASE_ACCOUNT_NOT_FOUND_CODE, new String[]{BizConsts.BASE_ACCOUNT_NOT_FOUND_MSG});
            } else {
                resps.setErr(BizConsts.BASE_SYS_ERR_CODE, new String[]{BizConsts.BASE_SYS_ERR_MSG});
            }
            response.getWriter().write(JsonUtil.obj2Json(resps));
            response.getWriter().close();
        });

        /**
         * 权限控制
         * ->无权限
         */
        http.authorizeRequests().antMatchers(
                "/",
                "/index").permitAll();
        /**
         * 权限控制
         * ->普通用户可访问
         */
        http.authorizeRequests().antMatchers(
                "/my/getIdent",
                "/my/addBankCard").hasRole("CONSUMER_REAL");
        /**
         * 权限控制
         * ->登录可访问
         */
        http.authorizeRequests().antMatchers(
                "/my/**").authenticated();

        // 登出处理。
        http.logout().logoutSuccessHandler((request, response, authentication) -> {
            redisTemplate0.delete(RedisConsts.CONSUMER_SESSION + "-" + authentication.getName());
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(JsonUtil.obj2Json(Resps.success()));
            response.getWriter().close();
        });

        // 未登录，却访问需要登录的接口时的处理
        http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(401);
            response.getWriter().write(
                    JsonUtil.obj2Json(
                            Resps.error(BizConsts.BASE_NOT_LOGIN_CODE, new String[]{BizConsts.BASE_NOT_LOGIN_CODE})
                    )
            );
            response.getWriter().close();
        });
        // 已登录，但当前用户没有访问的某个接口的权限时的处理
        http.exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
            response.setContentType("application/json;charset=UTF-8");
            response.setStatus(401);
            response.getWriter().write(
                    JsonUtil.obj2Json(
                            Resps.error(BizConsts.BASE_NOT_ROLE_CODE, new String[]{BizConsts.BASE_NOT_ROLE_MSG})
                    )
            );
            response.getWriter().close();
        });
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 不需要经过SpringSecurity的过滤器的URLs
        // web.ignoring();
    }

}
